#include <string.h>

#include <windows.h>
#include <winsock.h>

#define SECURITY_WIN32
#include <security.h>
#include <sspi.h>
#include <schannel.h>

#include "lib.h"
#include "ssl.h"

#ifndef SEC_I_CONTEXT_EXPIRED	// need updated WinError.h (win2k version)
#define SEC_I_CONTEXT_EXPIRED	((HRESULT)0x00090317L)
#endif

//#define DEBUG_DUMPINFO		// dump info about attributes and certificates
//#define DEBUG_DUMPIO			// dump all sent and recv'd data

static PSecurityFunctionTable	spSSL = (PSecurityFunctionTable)0;

HINSTANCE	SSL::shSSL			= (HINSTANCE)0;
int32		SSL::sMaxTokenSize	= 0;

class SSLContext
{
	public:
		SSLContext(int32 maxTokenSize)
		{
			ZeroMemory(&cred, sizeof(CredHandle));
			ZeroMemory(&context, sizeof(CtxtHandle));
			ZeroMemory(&strmSz, sizeof(SecPkgContext_StreamSizes));
			ibuf = new uint8[maxTokenSize];
			obuf = new uint8[maxTokenSize];
			ioBufSize = maxTokenSize;
			iUsed = 0;
			oUsed = 0;
			iDecrypt = (uint8*)0;
			oEncrypt = (uint8*)0;
			idUsed = 0;
			flags = 0;
		}

		~SSLContext()
		{
			if (flags & CTXT_VALID)
				spSSL->DeleteSecurityContext(&context);
		
			if (flags & CRED_VALID)
				spSSL->FreeCredentialsHandle(&cred);

			delete [] oEncrypt;
			delete [] iDecrypt;
			delete [] ibuf;
			delete [] obuf;
			ibuf = obuf = (uint8*)0;

			ioBufSize = 0;
			flags = 0;
		}

		bool8 initOK()
		{
			return (bool8)(ibuf != (uint8*)0 && obuf != (uint8*)0);
		}

		void setCredValid() { flags |= CRED_VALID; }
		void setCtxtValid() { flags |= CTXT_VALID; }
		void setNegotiating(bool8 val)
		{
			if (val)
				flags |= NEGOTIATING;
			else
				flags &= ~NEGOTIATING;
		}
		void setRecvHungry(bool8 val)
		{
			if (val)
				flags |= RECV_HUNGRY;
			else
				flags &= ~RECV_HUNGRY;
		}
		bool8 isCredValid() { return (bool8)((flags & CRED_VALID) != 0); }
		bool8 isCtxtValid() { return (bool8)((flags & CTXT_VALID) != 0); }
		bool8 isNegotiating() { return (bool8)((flags & NEGOTIATING) != 0); }
		bool8 isRecvHungry() { return (bool8)((flags & RECV_HUNGRY) != 0); }
		bool8 setConnected()
		{
			spSSL->QueryContextAttributes(
				&context, SECPKG_ATTR_STREAM_SIZES, &strmSz);

			iDecrypt = new uint8[
				strmSz.cbHeader + strmSz.cbMaximumMessage + strmSz.cbTrailer];
			oEncrypt = new uint8[
				strmSz.cbHeader + strmSz.cbMaximumMessage + strmSz.cbTrailer];

			idUsed = 0;

			return (bool8)(iDecrypt != (uint8*)0 && oEncrypt != (uint8*)0);
		}

	protected:
		enum Flags
		{
			CRED_VALID	= 0x00000001L,
			CTXT_VALID	= 0x00000002L,
			NEGOTIATING	= 0x00000004L,
			RECV_HUNGRY	= 0x00000008L
		};

	public:
		CredHandle	cred;
		CtxtHandle	context;
		uint8*		ibuf;
		uint8*		obuf;
		uint8*		iDecrypt;
		uint8*		oEncrypt;
		int32		ioBufSize;
		int32		iUsed;
		int32		oUsed;
		uint32		idUsed;
		uint32		flags;
		SecPkgContext_StreamSizes strmSz;
};

bool8
SSL::setup()
{
	if (!shSSL)
	{
		FARPROC initSSL;

		shSSL = LoadLibrary(TEXT("security.dll"));
		if (!shSSL)
			goto abort;

		initSSL = GetProcAddress(shSSL, SECURITY_ENTRYPOINT_ANSI);
		if (!initSSL)
			goto abort;

		spSSL = (PSecurityFunctionTable)initSSL();
		if (!spSSL)
			goto abort;
	}

	PSecPkgInfo pInfo;

	if (SEC_E_OK != spSSL->QuerySecurityPackageInfo(UNISP_NAME, &pInfo))
		goto abort;

	sMaxTokenSize = (int32)pInfo->cbMaxToken;

	spSSL->FreeContextBuffer(pInfo);

	return true;

abort:
	spSSL = (PSecurityFunctionTable)0;
	if (shSSL)
	{
		FreeLibrary(shSSL);
		shSSL = (HINSTANCE)0;
	}
	return false;
}

void
SSL::teardown()
{
	if (shSSL)
		FreeLibrary(shSSL);

	shSSL = (HINSTANCE)0;
	spSSL = (PSecurityFunctionTable)0;
	sMaxTokenSize = 0;
}

SSL::SSL( const String& searchStr )
{
	sock = INVALID_SOCKET;
	context = (SSLContext*)0;
	recvTimeout = 120;		// 2 minutes
	lastResult = Success;

	certSubjectStr = searchStr;
}

SSL::~SSL()
{
	close();

	delete context;
	context = (SSLContext*)0;

	if (sock != INVALID_SOCKET)
	{
		closesocket(sock);
		sock = INVALID_SOCKET;
	}

}

bool8
SSL::open(uint32 ipaddr, int32 port)
{
	SOCKADDR_IN addr;

	addr.sin_family = AF_INET;
    addr.sin_port = htons((short)port);
	addr.sin_addr.s_addr = htonl(ipaddr);

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET)
	{
		lastResult = E_InternalError;
		return false;
	}

	context = new SSLContext(sMaxTokenSize);
	if (!context || !context->initOK())
	{
		closesocket(sock);
		sock = INVALID_SOCKET;
		lastResult = E_InternalError;
		return false;
	}

	if (SOCKET_ERROR == ::connect(sock, (PSOCKADDR)&addr, sizeof(SOCKADDR_IN)))
	{
		DEBUGMSG(("connect failed (%d)", WSAGetLastError()));

		delete context;
		context = (SSLContext*)0;

		closesocket(sock);
		sock = INVALID_SOCKET;

		lastResult = E_ConnFailed;

		return false;
	}

	lastResult = Success;
	return true;
}

bool8
SSL::sendBytes(uint8* Msg, uint32 Len)
{
	uint32 totalSent;
	int sentNow;

	totalSent = 0;

	while (totalSent < Len)
	{
		sentNow = ::send(sock, (char*)Msg + totalSent, Len - totalSent, 0);
		if (SOCKET_ERROR == sentNow)
		{
			lastResult = E_InternalError;
			return false;
		}

		totalSent += sentNow;
	}

	lastResult = Success;
	return true;
}

bool8 SSL::recvBytes(uint8* Buf, uint32 MaxLen, uint32* nRead)
{
	fd_set fd;
	TIMEVAL tv;
	int result;

	// use select to implement timeout
	//
	FD_ZERO(&fd);
	FD_SET(sock, &fd);

	tv.tv_sec = recvTimeout;
	tv.tv_usec = 0;

	result = select(1, &fd, NULL, NULL, &tv);
	if (result == SOCKET_ERROR)
	{
		lastResult = E_InternalError;
		return false;
	}
	else if (result == 0)
	{
		lastResult = E_Timeout;
		return false;
	}

	*nRead = ::recv(sock, (char*)Buf, MaxLen, 0);
	if (*nRead == SOCKET_ERROR)
	{
		lastResult = E_InternalError;
		return false;
	}

	lastResult = Success;
	return true;
}

bool8 
SSL::negotiateContext()
{
	bool8 result, done, iNeeded;
	uint8 *ibuf, *obuf;
	uint32 iUsed, oUsed;
	uint32 iNow;

	done = false;
	iNeeded = false;
	iUsed = context->iUsed;
	oUsed = 0;
	ibuf = context->ibuf;
	obuf = context->obuf;

	while (!done)
	{
		if (iNeeded)
		{
			if (!recvBytes(ibuf + iUsed, context->ioBufSize - iUsed, &iNow))
				goto abort;		// recvBytes() sets lastResult

			if (iNow == 0)		// other side disconnected
			{
				lastResult = E_ConnClosed;
				goto abort;
			}

#if defined(DEBUG) && defined(DEBUG_DUMPIO)
			DEBUGMSG(("\n%d bytes of handshake data received", iNow));
			DumpHex(ibuf + iUsed, iNow);
#endif

			iUsed += iNow;
		}

		result = getClientContext(ibuf, &iUsed, obuf, &oUsed, &iNeeded, &done);
		if (result && oUsed)
			result = sendBytes(obuf, oUsed);
		if (!result)	// getClientContext & sendBytes each set lastResult
			goto abort;
#if defined(DEBUG) && defined(DEBUG_DUMPIO)
		else if (oUsed)
		{
			DEBUGMSG(("\n%d bytes of handshake data sent", oUsed));
			DumpHex(obuf, oUsed);
		}
#endif
	}

	context->iUsed = iUsed;
	lastResult = Success;

#if defined(DEBUG) && defined(DEBUG_DUMPINFO)
	showRemoteCertificate();
	showContext();
#endif

	// todo: verify remote certificate here (if necessary)

abort:
	return done;
}

/*
JF 2/27/02
Modified to not use SSL port because the Recover page is not an https page
*/
bool8 SSL::connect(uint32 ipaddr, int32 port)
{
	if (sock != INVALID_SOCKET)
	{
		lastResult = E_ConnAlready;
		return false;
	}

	if (!open(ipaddr, port))		// sets lastResult
		return false;

	if (port == 80) {
		// don't negotiate ssl
	}
	else {

		if (!getEmptyClientCredentials())
		{
			lastResult = E_InternalError;
			goto abort;
		}

		if (!negotiateContext())		// sets lastResult
			goto abort;

		if (!context->setConnected())
		{
			lastResult = E_InternalError;
			goto abort;
		}
	}

	lastResult = Success;
	return true;

abort:
	close();

	delete context;
	context = (SSLContext*)0;

	if (sock != INVALID_SOCKET)
	{
		closesocket(sock);
		sock = INVALID_SOCKET;
	}
	return false;
}

bool8
SSL::connect(char* hostname, int32 port)
{
	uint32 ipaddr = ntohl(inet_addr(hostname));

	if (ipaddr == INADDR_NONE)
	{
		HOSTENT* host = gethostbyname(hostname);
		if (host)
			ipaddr = ntohl(((IN_ADDR*)host->h_addr_list[0])->s_addr);
		else
		{
			lastResult = E_ConnFailed;
			return false;
		}
	}

	return connect(ipaddr, port);
}

bool8
SSL::send(uint8* msg, uint32 amt)
{
	uint8* out;
	uint32 hdr, msgSize, trailer;
	uint32 totalSent;
	SECURITY_STATUS ss;
	SecBufferDesc odesc;
	SecBuffer obuf[3];

	if (sock == INVALID_SOCKET || !context)
	{
		lastResult = E_InternalError;
		return false;
	}

	hdr = context->strmSz.cbHeader;
	msgSize = MIN(amt, context->strmSz.cbMaximumMessage);
	trailer = context->strmSz.cbTrailer;

	out = context->oEncrypt;

	totalSent = 0;

	while (totalSent < amt)
	{
		msgSize = MIN(amt - totalSent, context->strmSz.cbMaximumMessage);
		memcpy(out + hdr, msg, msgSize);
		msg += msgSize;

		odesc.ulVersion = SECBUFFER_VERSION;
		odesc.cBuffers = 3;
		odesc.pBuffers = obuf;
		obuf[0].BufferType = SECBUFFER_STREAM_HEADER;
		obuf[0].cbBuffer = hdr;
		obuf[0].pvBuffer = out;
		obuf[1].BufferType = SECBUFFER_DATA;
		obuf[1].cbBuffer = msgSize;
		obuf[1].pvBuffer = (uint8*)obuf[0].pvBuffer + hdr;
		obuf[2].BufferType = SECBUFFER_STREAM_TRAILER;
		obuf[2].cbBuffer = trailer;
		obuf[2].pvBuffer = (uint8*)obuf[1].pvBuffer + msgSize;

#if defined(DEBUG) && defined(DEBUG_DUMPIO)
		DEBUGMSG(("\n%d bytes of app data to send (clear text)", msgSize));
		DumpHex(out + hdr, msgSize);
#endif
		ss = spSSL->EncryptMessage(&context->context, 0, &odesc, 0);

		if (ss != SEC_E_OK)
		{
			// todo: EncryptMessage() may return SEC_I_RENEGOTIATE
			//		 problem: not documented how to proceed in this case
			//		 (and no example code)...do we send what EncryptMessage()
			//		 returns and THEN renegotiate or do we renegotiate and
			//		 then send what EncryptMessage() returns.  Also, does
			//		 EncryptMessage() change the buffer type to SECBUFFER_EXTRA
			//		 in this case?  Do we look for SECBUFFER_DATA and
			//		 SECBUFFER_EXTRA as in recv() for the normal case also?
			//
			DEBUGMSG(("EncryptMessage failed (0x%08x)", ss));
			lastResult = E_InternalError;
			goto abort;
		}

#if defined(DEBUG) && defined(DEBUG_DUMPIO)
		DEBUGMSG(("\n%d bytes of app data to send (encrypted)",
			hdr + msgSize + trailer));
		DumpHex(out, hdr + msgSize + trailer);
#endif

		if (!sendBytes(out, hdr + msgSize + trailer))	// sets lastResult
			goto abort;

		totalSent += msgSize;
	}

	lastResult = Success;

abort:
	return (bool8)(totalSent == amt);
}

bool8 SSL::recv(uint8* msg, uint32* amt)
{
	uint32 maxMsg = *amt;
	uint8* in;
	uint32 iUsed, iNow, i;
	SECURITY_STATUS ss;
	SecBufferDesc idesc;
	SecBuffer ibuf[4];
	ULONG qop;		// quality of protection flags
	bool8 wasClosed = false;

	if (sock == INVALID_SOCKET || !context)
	{
		lastResult = E_InternalError;
		return false;
	}

	if (context->idUsed)
	{
		if (*amt >= context->idUsed)
		{
			memcpy(msg, context->iDecrypt, context->idUsed);
			*amt = context->idUsed;
			context->idUsed = 0;
		}
		else
		{
			memcpy(msg, context->iDecrypt, *amt);
			memcpy(context->iDecrypt, context->iDecrypt + *amt,
				context->idUsed - *amt);
			context->idUsed -= *amt;
		}

		lastResult = Success;
		return true;
	}

	*amt = 0;

	in = context->ibuf;
	iUsed = context->iUsed;

	if (context->iUsed == 0 || context->isRecvHungry())
	{
		if (!recvBytes(in + iUsed, context->ioBufSize - iUsed, &iNow))
			return false;	// recvBytes() sets lastResult

		// if the connection was closed, ignore for now & try decrypting
		// until we can't proceed further--that way, the client receives
		// as much data as possible (maybe even all of the data).
		//
		if (iNow == 0)
			wasClosed = true;

		context->setRecvHungry(false);

		iUsed += iNow;
	}
	
	// decrypt as much of the input as possible
	//
	while (*amt < maxMsg && !context->isRecvHungry())
	{
		idesc.ulVersion = SECBUFFER_VERSION;
		idesc.cBuffers = 4;
		idesc.pBuffers = ibuf;
		ibuf[0].BufferType = SECBUFFER_DATA;
		ibuf[0].cbBuffer = iUsed;
		ibuf[0].pvBuffer = in;
		ibuf[1].BufferType = SECBUFFER_EMPTY;
		ibuf[2].BufferType = SECBUFFER_EMPTY;
		ibuf[3].BufferType = SECBUFFER_EMPTY;

#if defined(DEBUG) && defined(DEBUG_DUMPIO)
		DEBUGMSG(("\n%d bytes of app data received (encrypted)", iUsed));
		DumpHex(in, iUsed);
#endif

		ss = spSSL->DecryptMessage(&context->context, &idesc, 0, &qop);

		if (ss != SEC_E_OK)
		{
			if (ss == SEC_E_INCOMPLETE_MESSAGE)
			{
				context->setRecvHungry(true);
				continue;
			}

			if (ss != SEC_I_RENEGOTIATE &&
				ss != SEC_I_CONTEXT_EXPIRED)
			{
				DEBUGMSG(("DecryptMessage failed (0x%08x)", ss));
				lastResult = E_InternalError;
				goto abort;
			}
		}

		// find result
		//
		for (i = 0; i < 4; i++)
			if (ibuf[i].BufferType == SECBUFFER_DATA)
				break;

		if (i < 4 && ibuf[i].cbBuffer > 0)
		{
#if defined(DEBUG) && defined(DEBUG_DUMPIO)
			DEBUGMSG(("\n%d bytes of app data received (clear text)",
				ibuf[i].cbBuffer));
			DumpHex((uint8*)ibuf[i].pvBuffer, ibuf[i].cbBuffer);
#endif
			if (*amt + ibuf[i].cbBuffer <= maxMsg)
			{
				memcpy(msg + *amt, ibuf[i].pvBuffer, ibuf[i].cbBuffer);
				*amt += ibuf[i].cbBuffer;
			}
			else
			{
				int32 toCopy = maxMsg - *amt;

				memcpy(msg + *amt, ibuf[i].pvBuffer, toCopy);
				memcpy(context->iDecrypt,
					(uint8*)ibuf[i].pvBuffer + toCopy,
					ibuf[i].cbBuffer - toCopy);
				context->idUsed += ibuf[i].cbBuffer - toCopy;
				*amt += toCopy;
			}
		}

		// find left overs
		//
		for (i = 0; i < 4; i++)
			if (ibuf[i].BufferType == SECBUFFER_EXTRA)
				break;

		if (i < 4)
		{
#if defined(DEBUG) && defined(DEBUG_DUMPIO)
			DEBUGMSG(("\n%d bytes of app data EXTRA (encrypted)",
				ibuf[i].cbBuffer));
#endif
			memcpy(in, in + iUsed - ibuf[i].cbBuffer, ibuf[i].cbBuffer);
			iUsed = ibuf[i].cbBuffer;
		}
		else
			iUsed = 0;

		if (ss == SEC_I_CONTEXT_EXPIRED)
			break;

		if (ss == SEC_I_RENEGOTIATE)
		{
			DEBUGMSG(("Renegotiating..."));

			context->iUsed = iUsed;
			if (!negotiateContext())			// sets lastResult
			{
				DEBUGMSG(("Renegotiate failed"));
				goto abort;
			}
			iUsed = context->iUsed;
		}
	}

	context->iUsed = iUsed;

	if (wasClosed && context->idUsed == 0 && context->isRecvHungry())
	{
		// if connection was prematurely closed...return "closed" next
		// time (give caller as much decrypted info as possible)
		//
		lastResult = (*amt == 0) ? E_ConnClosed : Success;
	}
	else
		lastResult = Success;

	return (bool8)(lastResult == Success);

abort:
	return false;
}

void SSL::close()
{
	if (context && context->isCtxtValid())
	{
		DWORD           dwType;
		PBYTE           pbMessage;
		DWORD           cbMessage;

		SecBufferDesc   OutBuffer;
		SecBuffer       OutBuffers[1];
		DWORD           dwSSPIFlags;
		DWORD           dwSSPIOutFlags;
		TimeStamp       timestamp;
		SECURITY_STATUS ss;

		// Notify schannel that we are about to close the connection.
		//
		dwType = SCHANNEL_SHUTDOWN;

		OutBuffer.cBuffers  = 1;
		OutBuffer.pBuffers  = OutBuffers;
		OutBuffer.ulVersion = SECBUFFER_VERSION;
		OutBuffers[0].pvBuffer   = &dwType;
		OutBuffers[0].BufferType = SECBUFFER_TOKEN;
		OutBuffers[0].cbBuffer   = sizeof(DWORD);

		ss = spSSL->ApplyControlToken(&context->context, &OutBuffer);

		if (ss < 0)
		{
			DEBUGMSG(("**** ApplyControlToken failed (0x%08x)", ss));
			goto cleanup;
		}

		if (context->isCredValid())
		{
			// Build an SSL close notify message.
			//
			dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT   |
				ISC_REQ_REPLAY_DETECT     |
				ISC_REQ_CONFIDENTIALITY   |
				ISC_RET_EXTENDED_ERROR    |
				ISC_REQ_ALLOCATE_MEMORY   |
				ISC_REQ_STREAM;

			OutBuffer.cBuffers  = 1;
			OutBuffer.pBuffers  = OutBuffers;
			OutBuffer.ulVersion = SECBUFFER_VERSION;
			OutBuffers[0].pvBuffer   = NULL;
			OutBuffers[0].BufferType = SECBUFFER_TOKEN;
			OutBuffers[0].cbBuffer   = 0;

			ss = spSSL->InitializeSecurityContext(
				&context->cred,
				&context->context,
				NULL,
				dwSSPIFlags,
				0,
				SECURITY_NATIVE_DREP,
				NULL,
				0,
				&context->context,
				&OutBuffer,
				&dwSSPIOutFlags,
				&timestamp);

			pbMessage = (PBYTE)OutBuffers[0].pvBuffer;
			cbMessage = OutBuffers[0].cbBuffer;

			if (ss < 0)
			{
				DEBUGMSG(("**** InitializeSecurityContext (0x%08x)", ss));
				if (pbMessage)
					spSSL->FreeContextBuffer(pbMessage);
				goto cleanup;
			}

			// Send the close notify message to the server.
			//
			if (pbMessage != NULL && cbMessage != 0)
			{
				if (!sendBytes(pbMessage, cbMessage))
				{
					DEBUGMSG(("**** Failed to send close notify (%d)",
						WSAGetLastError()));
					spSSL->FreeContextBuffer(pbMessage);
					goto cleanup;
				}

				spSSL->FreeContextBuffer(pbMessage);
			}
		}
	}
    
cleanup:
	delete context;
	context = (SSLContext*)0;

	if (sock != INVALID_SOCKET)
	{
		closesocket(sock);
		sock = INVALID_SOCKET;
	}

	lastResult = Success;
}

bool8
SSL::getEmptyClientCredentials()
{
	if (!context)
		return false;

	if (!context->isCredValid())
	{
		SECURITY_STATUS ss;
		TimeStamp timestamp;
		SCHANNEL_CRED scred;

		ZeroMemory(&scred, sizeof(SCHANNEL_CRED));
		scred.dwVersion = SCHANNEL_CRED_VERSION;
		scred.dwFlags =
			SCH_CRED_NO_SERVERNAME_CHECK |
			SCH_CRED_MANUAL_CRED_VALIDATION |
			SCH_CRED_NO_DEFAULT_CREDS;	  //todo: allow user-supplied creds

		ss = spSSL->AcquireCredentialsHandle((SEC_WCHAR*)0,
			UNISP_NAME,
			SECPKG_CRED_OUTBOUND,
			(PLUID)0,
			&scred,
			(SEC_GET_KEY_FN)0,
			(PVOID)0,
			&context->cred,
			&timestamp);

		if (ss == SEC_E_OK)
			context->setCredValid();
		else
			DEBUGMSG(("AcquireCredentialsHandle failed (0x%08x)", ss));
	}

	return context->isCredValid();
}

void
SSL::getNewClientCredentials()
{
	HCERTSTORE		hCertStore		= NULL;
	PCCERT_CONTEXT	pCertContext	= NULL;
	CredHandle		newCred;
	SECURITY_STATUS	ss;
	SCHANNEL_CRED	scred;
	TimeStamp		timestamp;

	// If a search string was specified in the constructor, search for a 
	// matching certificate in the local machine store. Otherwise, perform
	// the old search.
	if( !certSubjectStr.isEmpty() )
	{
		// Open the local machine's "MY" certificate store
		hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 
								   X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
								   0,
								   CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READONLY_FLAG,
								   "MY");
		if (!hCertStore)
		{
			DEBUGMSG(("CertOpenStore failed (0x%08x)", GetLastError()));
			return;
		}

		// Search for a certificate whose subject string contains the 
		// provided search string
		pCertContext = CertFindCertificateInStore(hCertStore,
												  X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
												  0,
												  CERT_FIND_SUBJECT_STR,
												  (const uint16*)certSubjectStr,
												  NULL);
		if (!pCertContext)
		{			
			DEBUGMSG(("Failed to find certificate (0x%08x)", GetLastError()));
			return;
		}

		// Create schannel credential
		ZeroMemory(&scred, sizeof(SCHANNEL_CRED));
		scred.dwVersion = SCHANNEL_CRED_VERSION;
		scred.dwFlags =
			SCH_CRED_NO_SERVERNAME_CHECK |
			SCH_CRED_MANUAL_CRED_VALIDATION |
			SCH_CRED_NO_DEFAULT_CREDS;
		scred.cCreds = 1;
		scred.paCred = &pCertContext;

		ss = spSSL->AcquireCredentialsHandle((SEC_WCHAR*)0,
			UNISP_NAME,
			SECPKG_CRED_OUTBOUND,
			(PLUID)0,
			&scred,
			(SEC_GET_KEY_FN)0,
			(PVOID)0,
			&newCred,
			&timestamp);

		if (ss != SEC_E_OK)
		{
			DEBUGMSG(("AcquireCredentialsHandle failed (0x%08x)", ss));
			return;
		}

		// Destroy the old credentials
		spSSL->FreeCredentialsHandle(&context->cred);
	}
	else
	{
		SecPkgContext_IssuerListInfoEx	IssuerListInfo;
		PCCERT_CHAIN_CONTEXT			pChainContext;
		CERT_CHAIN_FIND_BY_ISSUER_PARA	FindByIssuerPara;

		// Read list of trusted issuers from schannel
		//
		ss = spSSL->QueryContextAttributes(
			&context->context,
			SECPKG_ATTR_ISSUER_LIST_EX,
			&IssuerListInfo);

		if (ss != SEC_E_OK)
		{
			DEBUGMSG(("Failed to get issuer list (0x08%x)", ss));
			return;
		}

		// Enumerate the client certificates
		//
		ZeroMemory(&FindByIssuerPara, sizeof(FindByIssuerPara));

		FindByIssuerPara.cbSize = sizeof(CERT_CHAIN_FIND_BY_ISSUER_PARA);
		FindByIssuerPara.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
		FindByIssuerPara.dwKeySpec = 0;
		FindByIssuerPara.cIssuer   = IssuerListInfo.cIssuers;
		FindByIssuerPara.rgIssuer  = IssuerListInfo.aIssuers;

		pChainContext = NULL;

		// Open the "MY" certificate store, which is where Internet Explorer
		// stores its client certificates.
		//
		hCertStore = CertOpenSystemStore(0, TEXT("MY"));
		if (!hCertStore)
		{
			DEBUGMSG(("CertOpenSystemStore failed (0x%08x)", GetLastError()));
			spSSL->FreeContextBuffer(IssuerListInfo.aIssuers);
			return;
		}

		while (TRUE)
		{
			// Find a certificate chain.
			//
			pChainContext = CertFindChainInStore(
				hCertStore,
				X509_ASN_ENCODING,
				0,
				CERT_CHAIN_FIND_BY_ISSUER,
				&FindByIssuerPara,
				pChainContext);

			if (!pChainContext)
			{
				DEBUGMSG(("Failed to find cert chain (0x%08x)", GetLastError()));
				break;
			}

			// Get pointer to leaf certificate context.
			//
			pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;

			// Create schannel credential.
			//
			ZeroMemory(&scred, sizeof(SCHANNEL_CRED));
			scred.dwVersion = SCHANNEL_CRED_VERSION;
			scred.dwFlags =
				SCH_CRED_NO_SERVERNAME_CHECK |
				SCH_CRED_MANUAL_CRED_VALIDATION |
				SCH_CRED_NO_DEFAULT_CREDS;
			scred.cCreds = 1;
			scred.paCred = &pCertContext;

			ss = spSSL->AcquireCredentialsHandle((SEC_WCHAR*)0,
				UNISP_NAME,
				SECPKG_CRED_OUTBOUND,
				(PLUID)0,
				&scred,
				(SEC_GET_KEY_FN)0,
				(PVOID)0,
				&newCred,
				&timestamp);

			if (ss != SEC_E_OK)
			{
				DEBUGMSG(("AcquireCredentialsHandle failed (0x%08x)", ss));
				continue;
			}

			CertFreeCertificateChain(pChainContext);

			// Destroy the old credentials.
			//
			spSSL->FreeCredentialsHandle(&context->cred);
			break;
		}

		spSSL->FreeContextBuffer(IssuerListInfo.aIssuers);

	}

	memcpy(&context->cred, &newCred, sizeof(CredHandle));

#if defined(DEBUG) && defined(DEBUG_DUMPINFO)
	if (pCertContext)
		showCertificate((void*)pCertContext);
#endif

	if (pCertContext)
		CertFreeCertificateContext(pCertContext);
	CertCloseStore(hCertStore, 0);
}

bool8
SSL::getClientContext(
	uint8* in, uint32* inAmt,
	uint8* out, uint32* outAmt,
	bool8* needInput,
	bool8* done)
{
	SECURITY_STATUS ss;
	TimeStamp timestamp;
	SecBufferDesc idesc, odesc;
	SecBuffer ibuf[2], obuf;
	DWORD secAttr, contextAttr;

	if (!context->isCredValid())
	{
		lastResult = E_InternalError;
		return false;
	}

	ZeroMemory(&ibuf[0], sizeof(SecBuffer));
	ZeroMemory(&ibuf[1], sizeof(SecBuffer));
	ZeroMemory(&obuf,	sizeof(SecBuffer));

	odesc.ulVersion = SECBUFFER_VERSION;
	odesc.cBuffers = 1;
	odesc.pBuffers = &obuf;
	obuf.BufferType = SECBUFFER_TOKEN;
	obuf.cbBuffer = 0;
	obuf.pvBuffer = NULL;

	if (context->isNegotiating())
	{
		idesc.ulVersion = SECBUFFER_VERSION;
		idesc.cBuffers = 2;
		idesc.pBuffers = ibuf;
		ibuf[0].BufferType = SECBUFFER_TOKEN;
		ibuf[0].cbBuffer = *inAmt;
		ibuf[0].pvBuffer = in;
		ibuf[1].BufferType = SECBUFFER_EMPTY;
		ibuf[1].cbBuffer = 0;
		ibuf[1].pvBuffer = NULL;
	}

	secAttr = ISC_REQ_SEQUENCE_DETECT |
		ISC_REQ_REPLAY_DETECT |
		ISC_REQ_CONFIDENTIALITY |
		ISC_REQ_EXTENDED_ERROR |
		ISC_REQ_ALLOCATE_MEMORY |	// doesn't seem to work without this :(
		ISC_REQ_STREAM;

	ss = spSSL->InitializeSecurityContext(
		&context->cred,
		context->isCtxtValid() ? &context->context : NULL,
		(SEC_WCHAR*)0,
		secAttr,
		0,
		SECURITY_NATIVE_DREP,
		context->isNegotiating() ? &idesc : NULL,
		0,
		&context->context,
		&odesc,
		&contextAttr,
		&timestamp);

	*outAmt = obuf.cbBuffer;
	if (obuf.cbBuffer)
	{
		memcpy(out, obuf.pvBuffer, obuf.cbBuffer);

		spSSL->FreeContextBuffer(obuf.pvBuffer);
	}
		
	if (ss < 0 &&
		ss != SEC_E_INCOMPLETE_MESSAGE &&
		ss != SEC_I_INCOMPLETE_CREDENTIALS)
	{
		if (ss == SEC_E_NOT_OWNER		|| ss == SEC_E_UNKNOWN_CREDENTIALS	||
			ss == SEC_E_NO_CREDENTIALS	|| ss == SEC_E_CERT_UNKNOWN			||
			ss == SEC_E_CERT_EXPIRED)
			lastResult = E_NotAuthorized;
		else
			lastResult = E_InternalError;
		DEBUGMSG(("InitializeSecurityContext failed (0x%08x)", ss));
		return false;
	}

	context->setCtxtValid();
	context->setNegotiating(true);

	if (ss == SEC_E_INCOMPLETE_MESSAGE)		// need more data, yum
	{
		// DO NOT change *inAmt!...just read more data onto the end
		// (the whole point of SEC_E_INCOMPLETE_MESSAGE :)
		//
		*needInput = true;
		*done = false;
		lastResult = Success;
		return true;
	}

	if (ss == SEC_E_OK)
	{
		if (ibuf[1].BufferType == SECBUFFER_EXTRA)
		{
			memcpy(in, in + *inAmt - ibuf[1].cbBuffer, ibuf[1].cbBuffer);
			*inAmt = ibuf[1].cbBuffer;
		}
		else
			*inAmt = 0;

#if defined(DEBUG) && defined(DEBUG_DUMPINFO)
		showContextCaps(contextAttr);
#endif
		//todo: check that we got the context that we expect (has req attribs)

		context->setNegotiating(false);

		*needInput = false;
		*done = true;
		lastResult = Success;

		return true;
	}

	*done = false;

	// prepare any unprocessed input for the next go 'round
	//
	if (ibuf[1].BufferType == SECBUFFER_EXTRA && ibuf[1].cbBuffer > 0)
	{
		memcpy(in, in + *inAmt - ibuf[1].cbBuffer, ibuf[1].cbBuffer);
		*inAmt = ibuf[1].cbBuffer;
		*needInput = false;		// seems to be ssl-specific
	}
	else
	{
		*inAmt = 0;
		*needInput = true;
	}

	if (ss == SEC_I_INCOMPLETE_CREDENTIALS)
	{
		DEBUGMSG(("SEC_I_INCOMPLETE_CREDENTIALS..."
			"getting specific credentials"));
		getNewClientCredentials();
		*needInput = false;
	}

	lastResult = Success;
	return true;
}

#ifdef DEBUG
void
SSL::showContext()
{
	SECURITY_STATUS ss;
	SecPkgContext_ConnectionInfo ci;

	ss = spSSL->QueryContextAttributes(
		&context->context,
		SECPKG_ATTR_CONNECTION_INFO,
		(PVOID)&ci);

	if (ss != SEC_E_OK)
	{
		DEBUGMSG(("Error querying connection info (0x%08x)", ss));
		return;
	}

	DEBUGMSG((""));

	switch(ci.dwProtocol)
	{
		case SP_PROT_TLS1_CLIENT: DEBUGMSG(("Protocol: TLS1")); break;
		case SP_PROT_SSL3_CLIENT: DEBUGMSG(("Protocol: SSL3")); break;
		case SP_PROT_PCT1_CLIENT: DEBUGMSG(("Protocol: PCT")); break;
		case SP_PROT_SSL2_CLIENT: DEBUGMSG(("Protocol: SSL2")); break;
		default: DEBUGMSG(("Protocol: 0x%08x", ci.dwProtocol)); break;
	}

	switch(ci.aiCipher)
	{
		case CALG_RC4:			DEBUGMSG(("Cipher: RC4")); break;
		case CALG_3DES:			DEBUGMSG(("Cipher: Triple DES")); break;
		case CALG_RC2:			DEBUGMSG(("Cipher: RC2")); break;
		case CALG_DES: 
		case CALG_CYLINK_MEK:	DEBUGMSG(("Cipher: DES")); break;
		case CALG_SKIPJACK:		DEBUGMSG(("Cipher: Skipjack")); break;
		default:				DEBUGMSG(("Cipher: 0x%08x", ci.aiCipher));break;
	}

	DEBUGMSG(("Cipher strength: %d", ci.dwCipherStrength));

	switch(ci.aiHash)
	{
		case CALG_MD5:			DEBUGMSG(("Hash: MD5")); break;
		case CALG_SHA:			DEBUGMSG(("Hash: SHA")); break; 
		default:				DEBUGMSG(("Hash: 0x%08x", ci.aiHash)); break;
	}

	DEBUGMSG(("Hash strength: %d", ci.dwHashStrength));

	switch(ci.aiExch)
	{
		case CALG_RSA_KEYX: 
		case CALG_RSA_SIGN: DEBUGMSG(("Key exchange: RSA")); break;
		case CALG_KEA_KEYX: DEBUGMSG(("Key exchange: KEA")); break;
		case CALG_DH_EPHEM: DEBUGMSG(("Key exchange: DH Ephemeral")); break;
		default:			DEBUGMSG(("Key exchange: 0x%08x", ci.aiExch));break;
	}

	DEBUGMSG(("Key exchange strength: %d", ci.dwExchStrength));
}

void
SSL::showContextCaps(uint32 caps)
{
	DEBUGMSG(("\nContext capabilities:"));

	if (caps & ISC_RET_DELEGATE)
		DEBUGMSG(("    ISC_RET_DELEGATE"));
	if (caps & ISC_RET_MUTUAL_AUTH)
		DEBUGMSG(("    ISC_RET_MUTUAL_AUTH"));
	if (caps & ISC_RET_REPLAY_DETECT)
		DEBUGMSG(("    ISC_RET_REPLAY_DETECT"));
	if (caps & ISC_RET_SEQUENCE_DETECT)
		DEBUGMSG(("    ISC_RET_SEQUENCE_DETECT"));
	if (caps & ISC_RET_CONFIDENTIALITY)
		DEBUGMSG(("    ISC_RET_CONFIDENTIALITY"));
	if (caps & ISC_RET_USE_SESSION_KEY)
		DEBUGMSG(("    ISC_RET_USE_SESSION_KEY"));
	if (caps & ISC_RET_USED_COLLECTED_CREDS)
		DEBUGMSG(("    ISC_RET_USED_COLLECTED_CREDS"));
	if (caps & ISC_RET_USED_SUPPLIED_CREDS)
		DEBUGMSG(("    ISC_RET_USED_SUPPLIED_CREDS"));
	if (caps & ISC_RET_ALLOCATED_MEMORY)
		DEBUGMSG(("    ISC_RET_ALLOCATED_MEMORY"));
	if (caps & ISC_RET_USED_DCE_STYLE)
		DEBUGMSG(("    ISC_RET_USED_DCE_STYLE"));
	if (caps & ISC_RET_DATAGRAM)
		DEBUGMSG(("    ISC_RET_DATAGRAM"));
	if (caps & ISC_RET_CONNECTION)
		DEBUGMSG(("    ISC_RET_CONNECTION"));
	if (caps & ISC_RET_INTERMEDIATE_RETURN)
		DEBUGMSG(("    ISC_RET_INTERMEDIATE_RETURN"));
	if (caps & ISC_RET_CALL_LEVEL)
		DEBUGMSG(("    ISC_RET_CALL_LEVEL"));
	if (caps & ISC_RET_EXTENDED_ERROR)
		DEBUGMSG(("    ISC_RET_EXTENDED_ERROR"));
	if (caps & ISC_RET_STREAM)
		DEBUGMSG(("    ISC_RET_STREAM"));
	if (caps & ISC_RET_INTEGRITY)
		DEBUGMSG(("    ISC_RET_INTEGRITY"));
	if (caps & ISC_RET_IDENTIFY)
		DEBUGMSG(("    ISC_RET_IDENTIFY"));
	if (caps & ISC_RET_NULL_SESSION)
		DEBUGMSG(("    ISC_RET_NULL_SESSION"));
	if (caps & ISC_RET_MANUAL_CRED_VALIDATION)
		DEBUGMSG(("    ISC_RET_MANUAL_CRED_VALIDATION"));
	if (caps & ISC_RET_RESERVED1)
		DEBUGMSG(("    ISC_RET_RESERVED1"));
	if (caps & ISC_RET_FRAGMENT_ONLY)
		DEBUGMSG(("    ISC_RET_FRAGMENT_ONLY"));
}

void
SSL::showCertificate(void* certctx)
{
	TCHAR szName[1024];
	PCCERT_CONTEXT pCurrentCert;
	PCCERT_CONTEXT pIssuerCert;
	DWORD dwVerificationFlags;

	pCurrentCert = (PCCERT_CONTEXT)certctx;

	// display leaf name
	//
	if (!CertNameToStr(
			pCurrentCert->dwCertEncodingType,
			&pCurrentCert->pCertInfo->Subject,
			CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
			szName, sizeof(szName)))
	{
		DEBUGMSG(("**** Failed to build subject name (0x%08x)",
			GetLastError()));
		szName[0] = '\0';
	}

	DEBUGMSG(("Subject: %s", szName));

	if (!CertNameToStr(
		pCurrentCert->dwCertEncodingType,
		&pCurrentCert->pCertInfo->Issuer,
		CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
		szName, sizeof(szName)))
	{
		DEBUGMSG(("**** Failed to build issuer name (0x%08x)", GetLastError()));
		szName[0] = '\0';
	}

	DEBUGMSG(("Issuer: %s", szName));

	// display certificate chain
	//
	while (pCurrentCert)
	{
		dwVerificationFlags = 0;

		pIssuerCert = CertGetIssuerCertificateFromStore(
			((PCCERT_CONTEXT)certctx)->hCertStore,
			pCurrentCert,
			NULL,
			&dwVerificationFlags);

		if (!pIssuerCert)
		{
			if (pCurrentCert != (PCCERT_CONTEXT)certctx)
				CertFreeCertificateContext(pCurrentCert);

			break;
		}

		if (!CertNameToStr(
			pIssuerCert->dwCertEncodingType,
			&pIssuerCert->pCertInfo->Subject,
			CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
			szName, sizeof(szName)))
		{
			DEBUGMSG(("**** Failed to build subject name (0x%08x)",
				GetLastError()));
			szName[0] = '\0';
		}

		DEBUGMSG(("CA subject: %s", szName));

		if (!CertNameToStr(
			pIssuerCert->dwCertEncodingType,
			&pIssuerCert->pCertInfo->Issuer,
			CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
			szName, sizeof(szName)))
		{
			DEBUGMSG(("**** Failed to build issuer name", GetLastError()));
			szName[0] = '\0';
		}

		DEBUGMSG(("CA issuer: %s\n", szName));

		if (pCurrentCert != (PCCERT_CONTEXT)certctx)
			CertFreeCertificateContext(pCurrentCert);

		pCurrentCert = pIssuerCert;
		pIssuerCert = NULL;
	}
}

void
SSL::showRemoteCertificate()
{
	SECURITY_STATUS ss;
	PCCERT_CONTEXT pRemoteCert = (PCCERT_CONTEXT)0;

	ss = spSSL->QueryContextAttributes(
		&context->context,
		SECPKG_ATTR_REMOTE_CERT_CONTEXT,
		(PVOID)&pRemoteCert);

	if (ss != SEC_E_OK)
	{
		DEBUGMSG(("**** Error querying remote certificate (0x%08x)", ss));
		return;
	}

	showCertificate((void*)pRemoteCert);

	CertFreeCertificateContext(pRemoteCert);
}

#include <stdlib.h>
void
SSL::DumpHex(uint8* buffer, uint32 length)
{
	DWORD i,count,index;
	CHAR rgbDigits[]="0123456789abcdef";
	CHAR rgbLine[100];
	char cbLine;
	char hexindex[30];

	for (index = 0; length; length -= count, buffer += count, index += count) 
	{
		count = (length > 16) ? 16:length;

		cbLine = 0;

		ultoa(index, hexindex, 16);
		i = strlen(hexindex);
		while (i < 4)
		{
			rgbLine[cbLine++] = '0';
			++i;
		}
		strcpy(rgbLine + cbLine, hexindex);
		cbLine += strlen(hexindex);
		rgbLine[cbLine++] = ' ';
		rgbLine[cbLine++] = ' ';

		for(i=0;i<count;i++) 
		{
			rgbLine[cbLine++] = rgbDigits[buffer[i] >> 4];
			rgbLine[cbLine++] = rgbDigits[buffer[i] & 0x0f];
			if(i == 7) 
			{
				rgbLine[cbLine++] = ':';
			} 
			else 
			{
				rgbLine[cbLine++] = ' ';
			}
		}
		for(; i < 16; i++) 
		{
			rgbLine[cbLine++] = ' ';
			rgbLine[cbLine++] = ' ';
			rgbLine[cbLine++] = ' ';
		}

		rgbLine[cbLine++] = ' ';

		for(i = 0; i < count; i++) 
		{
			if(buffer[i] < 32 || buffer[i] > 126) 
			{
				rgbLine[cbLine++] = '.';
			} 
			else 
			{
				rgbLine[cbLine++] = buffer[i];
			}
		}

		rgbLine[cbLine++] = 0;
		DEBUGMSG(("%s", rgbLine));
	}
}
#endif
